using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Drawing.Imaging;
using System.Xml;
using Clandestine.Entities;
using Clandestine.Battle;
using System.Threading;
using System.Reflection;

namespace Clandestine
{
    public delegate void TriggerDelegate();

    public class Level
    {
        List<Sprite> quads = new List<Sprite>();

        public Dictionary<string, TriggerDelegate> Delegates = new Dictionary<string, TriggerDelegate>();
        public EntityManager Entities = new EntityManager();
        public int Width = 0;
        public int Height = 0;

        private int gridSize = 16;
        public int GridSize
        {
            get { return gridSize; }
        }

        private int bigQuadSize
        {
            get { return gridSize * 64; } // was *16
        }

        private static Dictionary<string, Type> classedLevels = null;

        private bool visible = true;
        public bool Visible
        {
            get { return visible; }
            set
            {
                lock (quads)
                    foreach (Sprite s in quads)
                        s.Visible = value;
                visible = value;
            }
        }

        private static void buildClassedLevelsList()
        {
            classedLevels = new Dictionary<string, Type>();

            foreach (Type t in Assembly.GetAssembly(typeof(Level)).GetTypes())
                if (t.IsSubclassOf(typeof(Level)) && !t.IsAbstract)
                    foreach (object o in t.GetCustomAttributes(false))
                        if (o is LevelHandlesFileAttribute)
                            classedLevels.Add(((LevelHandlesFileAttribute)o)
                                .HandledFilename.ToLower(), t);
        }

        public static Level GetLevel(string filename)
        {
            if (classedLevels == null)
                buildClassedLevelsList();

            if (classedLevels.ContainsKey(filename.ToLower()))
                return ((Level)Activator.CreateInstance(classedLevels[filename.ToLower()]));
            else
                return new Level(filename);
        }

        public Sprite[] GetMegaTiles()
        {
            return quads.ToArray();
        }

        public Level(string filename)
        {
            if (classedLevels != null && classedLevels.ContainsKey(filename.ToLower()))
                throw new Exception("This level xml file has an associated class!\n"
                    + "Stop using new Level(filename), start using Level.GetLevel(filename).");

            // Open the file in storedFilename
            XmlTextReader xml = new XmlTextReader(File.Open(filename, FileMode.Open));

            // Setup some varrrrs!
            Dictionary<int, Bitmap> spritesheets = new Dictionary<int, Bitmap>();

            // Read that bitch.
            while (xml.Read())
            {
                // We only care about start nodes here, rly
                if (xml.NodeType == XmlNodeType.Element)
                {
                    switch (xml.Name)
                    {
                        case "map":

                            this.Width = int.Parse(xml.GetAttribute("width"));
                            this.Height = int.Parse(xml.GetAttribute("height"));

                            if (xml.AttributeCount > 2)
                                this.gridSize = int.Parse(xml.GetAttribute("gridsize"));
                            else
                            {
                                this.gridSize = 16;
                                Log.e("No grid size, assuming 16x16.");
                                Log.e("Support for default grid sizes are going away VERY soon!");
                            }

                            break;
                        case "spritesheets":

                            // Okay, now we make a new little loop till we've loaded all spritesheets
                            while ((xml.NodeType != XmlNodeType.EndElement) || (xml.Name != "spritesheets"))
                            {
                                if ((xml.NodeType == XmlNodeType.Element) && (xml.Name == "spritesheet"))
                                {
                                	// Get filename of spritesheet and make sure it's unix friendly, i.e. / not \
                                	// Hack for now, but there be better ways!
                                	string name = xml.GetAttribute("name").Replace("\\", "/");
                                
                                    if (!Texture.TextureExists(name) && !File.Exists(name))
                                    {
                                        Log.w("Level contains reference to spritesheet '" + name + "', which cannot be found!");
                                        Texture.SetTexture(name, Texture.GetTexture("errorspritesheet.jpg"), true);
                                    }
                                    
                                    
                                    spritesheets.Add(spritesheets.Count,
                                                    (Texture.TextureExists(name) ? Texture.GetTexture(name).Bitmap
                                                                                                : new Bitmap(name)));
                                }
                                xml.Read();
                            }

                            break;
                        case "layers":

                            // Okay, now we make a new little loop till we've loaded all the layers
                            while ((xml.NodeType != XmlNodeType.EndElement) || (xml.Name != "layers"))
                            {
                                if ((xml.NodeType == XmlNodeType.Element) && (xml.Name == "layer"))
                                    this.parseLayerData(spritesheets, float.Parse(xml.GetAttribute("depth")), xml.ReadElementContentAsString().Split(new string[]{"\r\n"}, StringSplitOptions.None));
                                xml.Read();
                            }

                            break;
                        case "entities":
                            if (!xml.IsEmptyElement) // <entities/> :/
                                loadEntities(xml); // It's neater in another function...
                            break;
                    }
                }
            }

            xml.Close();
        }

        ~Level()
        {
            Destroy();
        }

        public void Destroy()
        {
            lock (quads)
            {
                foreach (Sprite s in quads)
                {
                    s.Dispose();
                    s.GetTexture().Destroy();
                }
                quads.Clear();
            }
            Entities.UnloadAllEntities();
        }

        public static void SwitchLevel(Level current, string filename)
        {
            current.Destroy();
            current = Level.GetLevel(filename);
        }


        private void loadEntities(XmlTextReader xml)
        {
            // Go through till </entities>
            while (!((xml.NodeType == XmlNodeType.EndElement) && (xml.Name == "entities")))
            {
                // New entity? -- Should be...
                if ((xml.NodeType == XmlNodeType.Element) && (xml.Name == "entity"))
                {
                    Entity e = (Entity)System.Activator.CreateInstance(xml.GetAttribute("assembly"), xml.GetAttribute("type")).Unwrap();

                    // Go through and add properties
                    while (!((xml.NodeType == XmlNodeType.EndElement) && (xml.Name == "entity")))
                    {
                        if (xml.NodeType == XmlNodeType.Element)
                        {
                            // Size
                            if (xml.Name == "size")
                                if (e.SupportsResizing)
                                    e.Size = new Size(int.Parse(xml.GetAttribute("width")), int.Parse(xml.GetAttribute("height")));
                                else
                                    Log.e("Map file tries to set .Size of an Entity ({0}) that does not support resizing!", e.GetType().FullName);

                            // Position
                            if (xml.Name == "position")
                                e.Position = new Point(int.Parse(xml.GetAttribute("x")), int.Parse(xml.GetAttribute("y")));

                            // Generic property
                            if (xml.Name == "property")
                            {
                                string name = xml.GetAttribute("name");
                                string strValue = xml.GetAttribute("value");
                                object value = null;
                                if (e.GetType().GetProperty(name).PropertyType == typeof(Rectangle))
                                {
                                    //haxy rectangle loading! :) 
                                    //A string that contains the position, width, and height of this Rectangle structure,for example, {X=a, Y=b, Width=c, Height=d} 
                                    string[] sc = strValue.Split(new string[] { " ; ", ",", "}", "{" }, StringSplitOptions.RemoveEmptyEntries);
                                    int a = int.Parse(sc[0].Substring(2, sc[0].Length - 2));
                                    int b = int.Parse(sc[1].Substring(2, sc[1].Length - 2));
                                    int c = int.Parse(sc[2].Substring(6, sc[2].Length - 6));
                                    int d = int.Parse(sc[3].Substring(7, sc[3].Length - 7));
                                    value = new Rectangle(a, b, c, d);
                                }
                                else if (e.GetType().GetProperty(name).PropertyType.IsEnum)
                                    value = Enum.Parse(e.GetType().GetProperty(name).PropertyType, strValue);
                                else
                                    value = Convert.ChangeType(strValue, e.GetType().GetProperty(name).PropertyType);
                                e.GetType().GetProperty(name).SetValue(e, value, null);
                            }
                        }
                        xml.Read();
                    }

                    Entities.AddEntity(e);
                }

                xml.Read();
            }
        }

        private void parseLayerData(Dictionary<int, Bitmap> spritesheets, float depth, string[] lines)
        {
            // Vars
            List<Bitmap> currentScanlineBigTextures = new List<Bitmap>();
            int tiley = 0;
            DateTime dtStart = DateTime.Now;

            // Do some funky shizz on each line!
            foreach (string line in lines)
            {
                // Otherwise...
                string[] scanlineTiles = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                if (scanlineTiles.Length < 1)
                    continue;

                if (tiley % (bigQuadSize / gridSize) == 0)
                {
                    // Commit textures.
                    for (int bmpX = 0; bmpX < currentScanlineBigTextures.Count; bmpX++)
                    {
                        Sprite sp = new Sprite(new Texture(currentScanlineBigTextures[bmpX]));
                        sp.X = bmpX * bigQuadSize;
                        sp.Y = (tiley - (bigQuadSize / gridSize)) * gridSize;
                        sp.Layer = depth;
                        quads.Add(sp);
                    }
                    currentScanlineBigTextures.Clear();
                }

                for (int tilex = 0; tilex < scanlineTiles.Length; tilex++)
                {
                    string tileString = scanlineTiles[tilex];

                    if (tileString.StartsWith(";") || tileString.StartsWith(".") || tileString.StartsWith("-"))
                        continue;

                    int spritesheetnumber;
                    int tilenumber;

                    if (tileString.Contains("."))
                    {
                        spritesheetnumber = int.Parse(tileString.Split('.')[0]);
                        tilenumber = int.Parse(tileString.Split('.')[1]);
                    }
                    else
                    {
                        spritesheetnumber = 0;
                        tilenumber = int.Parse(tileString);
                    }

                    Bitmap spritesheet = spritesheets[spritesheetnumber];

                    int bdstnum = tilex * gridSize / bigQuadSize; // integer division is good! (rounds down)
                    while (bdstnum >= currentScanlineBigTextures.Count)
                        currentScanlineBigTextures.Add(new Bitmap(bigQuadSize, bigQuadSize));
                    Rectangle srcRect = new Rectangle((tilenumber * gridSize)
                        % (spritesheet.Width - (spritesheet.Width % gridSize)),
                        gridSize * (tilenumber * gridSize
                        / (spritesheet.Width - (spritesheet.Width % gridSize))), gridSize, gridSize);
                    BitmapData bdSrc = spritesheet.LockBits(srcRect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                    Rectangle dstRect = new Rectangle((tilex * gridSize) % bigQuadSize,
                        (tiley * gridSize) % bigQuadSize, gridSize, gridSize);
                    BitmapData bdDst = currentScanlineBigTextures[bdstnum].LockBits(
                        dstRect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);

                    unsafe
                    {
                        byte* src = (byte*)bdSrc.Scan0.ToPointer();
                        byte* dst = (byte*)bdDst.Scan0.ToPointer();

                        for (int y = 0; y < gridSize; y++)
                        {
                            for (int b = 0; b < gridSize * 4; b++)
                            {
                                *(dst + b) = *(src + b);
                            }

                            src += bdSrc.Stride;
                            dst += bdDst.Stride;
                        }
                    }

                    currentScanlineBigTextures[bdstnum].UnlockBits(bdDst);
                    spritesheet.UnlockBits(bdSrc);
                }

                tiley++;
            }

            // From this point off, it's as if the old "end layer" occurs

            // Commit textures.
            for (int bmpX = 0; bmpX < currentScanlineBigTextures.Count; bmpX++)
            {
                Sprite sp = new Sprite(new Texture(currentScanlineBigTextures[bmpX]));
                sp.X = bmpX * bigQuadSize;
                sp.Y = (tiley - (tiley % (bigQuadSize / gridSize))) * gridSize;
                sp.Layer = depth;
                quads.Add(sp);
            }
            currentScanlineBigTextures.Clear();

            // Debug
            DateTime dtEnd = DateTime.Now;
            Log.i("Loading & constructing quads of tiles for depth level " + depth.ToString()
                + " took " + (dtEnd - dtStart).TotalMilliseconds.ToString() + "ms.");
        }

        
    }
}
